﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Collections;


/************************************************************************************************************
 * NameSpace Name:
 *  GpClassifier 
 * 
 * Content: 
 *  Contains main algorithm use Genetic Programming.
 *  Have for main function:
 *  -   Initials population
 *  -   Crossover
 *  -   Mutation
 *  -   Fitness
 ***********************************************************************************************************/
namespace GpClassifier
{
    /********************************************************************************************************
     * Class Name:
     *  GpClassifierGUI
     *  
     * Content:
     *  Contain GUI and Main algorithm.
     *******************************************************************************************************/
    public partial class GpClassifierGUI : Form
    {
        /**
	     * Contain Matrix of Score Data. 
         * - Row length: maximum 1416 rows. 
         * - Column length: maximum 54 column.
	     */
        private static String[,] dataList;

        /**
	     * List of training set - only store indexes.
	     */
        private static ArrayList trainIndex;

        /**
	     * List of test set - only store indexes.
	     */
        private static ArrayList testIndex;

        /**
	     * Range index of TN, CTN, BTH
	     */
        private const int MIN_INDEX_TN = 0;
        private const int MAX_INDEX_TN = 471;
        private const int MIN_INDEX_CTN = 472;
        private const int MAX_INDEX_CTN = 943;
        private const int MIN_INDEX_BTH = 944;
        private const int MAX_INDEX_BTH = 1415;

        /**
	     * Operator value
         *  - 0 -> 3: apply for contiguous data.
         *  - 4 -> 5: apply for discrete data.
	     */
        private const int LESSER = 0;
        private const int GREATER = 1;
        private const int LESSER_AND_EQUAL = 2;
        private const int GREATER_AND_EQUAL = 3;
        private const int EQUAL = 4;
        private const int NOT_EQUAL = 5;

        /**
         * Population of GP
         */
        // CTN
        GpData CTNSet1;
        GpData CTNSet2;
        GpData CTNSet3;
        GpData CTNSet4;
        // TN
        GpData TNSet1;
        GpData TNSet2;
        GpData TNSet3;
        GpData TNSet4;
        // BTH
        GpData BTHSet1;
        GpData BTHSet2;
        GpData BTHSet3;
        GpData BTHSet4;

        /**
	     * Index of row in Matrix.
	     */
        private static int index;

        /**
         * API Name:
         *  GpClassifierGUI
         *  
         * Content:
	     *  -   Initials data: 
         *      +   Create fixed matrix with 1500 rows and 55 columns. 
         *      +   set index equal 0. 
         *      +   initial trainIndex list. 
         *      +   initial testIndex list.
	     */
        public GpClassifierGUI()
        {
            InitializeComponent();
            dataList = new String[1500, 55];
            index = 0;
            trainIndex = new ArrayList();
            testIndex = new ArrayList();
        }

        private void aboutButton_Click(object sender, EventArgs e)
        {
        }

        private void openTrainDataBut_Click(object sender, EventArgs e)
        {
            // open new dialog to notification open data file
            OpenFileDialog openDialog = new OpenFileDialog();

            // filter data file only have .gpd
            openDialog.Filter = "Genetic Programming Data Files (.gpd)|*.gpd";
            openDialog.FilterIndex = 1; 

            // only select one file at once.
            openDialog.Multiselect = false;

            // result when user click on button.
            DialogResult userClickedOk = openDialog.ShowDialog();
            
            // user is chosen a file
            if (userClickedOk == DialogResult.OK)
            {
                // Name of file is selected
                String fileName = openDialog.FileName;

                // Load GP Data from file
                loadGPData(fileName);

                // Initials GP algorithm for contiguous data.
                initGpDataContiguous();
            }
        }

        /**
         * API Name:
         *  loadGPData
         * 
         * Content:
	     *  Load Score data from file.
	     * 
	     * Parameter:
         *  -   dataAddr: store file path of data
         * 
	     * Return:
         *  -   0: success loaded. 
         *  -   1: fail loaded.
	     */
        public int loadGPData(String dataAddr)
        {
            try
            {
                // reader to read .gpd file
                StreamReader reader = new StreamReader(dataAddr);

                // store line in file
                String line;

                // store content in a line when split with ','
                String[] content;

                // read file to end
                while ((line = reader.ReadLine()) != null)
                {
                    // split line by ','
                    content = line.Split(',');

                    // add to data table
                    for (int i = 0; i < content.Length; ++i)
                    {
                        dataList[index, i] = content[i];
                    }

                    // increase to the next line
                    ++index;
                }
                
                // if read file success
                return 0;
            }
            catch (System.IO.FileNotFoundException)
            {
                // read file fail
                return -1;
            }
        }

        /**
         * API Name:
         *  initGpDataContiguous
         *  
         * Content:
	     *  Initials begin population with 3 types: CTN, TN, BTH. And each type have:
	     *      -   low rule: only have 1 rule in rule list. 
         *      -   medium rule: have 26 rule in rule list. 
         *      -   full rule: have 54 rule in rule list. 
         *      -   Increment rule: increasing from 1 -> 10 rule in rule list.
	     */
        private void initGpDataContiguous()
        {
            // Random number
            Random random = new Random();

            // Data structure to node in rule list
            DataRule data = new DataRule();

            // rule list in individual.
            Hashtable hashtable;

            // CTN - low rule
            CTNSet1 = new GpData();
            CTNSet1.resultPredict = "CTN";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                data.setOperatorRule(random.Next(4));
                data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                int key = random.Next(54);
                while (hashtable.ContainsKey(key))
                {
                    key = random.Next(54);
                }
                hashtable[key] = data;
                CTNSet1.rules.Add(hashtable);
            }

            // CTN - medium rule
            CTNSet2 = new GpData();
            CTNSet2.resultPredict = "CTN";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                for (int j = 0; j < 28; ++j)
                {
                    data.setOperatorRule(random.Next(4));
                    data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                    int key = random.Next(54);
                    while (hashtable.ContainsKey(key))
                    {
                        key = random.Next(54);
                    }
                    hashtable[key] = data;
                }
                CTNSet2.rules.Add(hashtable);
            }

            // CTN - full rule
            CTNSet3 = new GpData();
            CTNSet3.resultPredict = "CTN";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                for (int j = 0; j < 54; ++j)
                {
                    data.setOperatorRule(random.Next(4));
                    data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                    int key = random.Next(54);
                    while (hashtable.ContainsKey(key))
                    {
                        key = random.Next(54);
                    }
                    hashtable[key] = data;
                }
                CTNSet3.rules.Add(hashtable);
            }

            // CTN - increment rule
            CTNSet4 = new GpData();
            CTNSet4.resultPredict = "CTN";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                for (int j = 0; j < i + 1; ++j)
                {
                    data.setOperatorRule(random.Next(4));
                    data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                    int key = random.Next(54);
                    while (hashtable.ContainsKey(key))
                    {
                        key = random.Next(54);
                    }
                    hashtable[key] = data;
                }
                CTNSet4.rules.Add(hashtable);
            }

            // TN - low rule
            TNSet1 = new GpData();
            TNSet1.resultPredict = "TN";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                data.setOperatorRule(random.Next(4));
                data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                int key = random.Next(54);
                while (hashtable.ContainsKey(key))
                {
                    key = random.Next(54);
                }
                hashtable[key] = data;
                TNSet1.rules.Add(hashtable);
            }

            // TN - medium rule
            TNSet2 = new GpData();
            TNSet2.resultPredict = "TN";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                for (int j = 0; j < 28; ++j)
                {
                    data.setOperatorRule(random.Next(4));
                    data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                    int key = random.Next(54);
                    while (hashtable.ContainsKey(key))
                    {
                        key = random.Next(54);
                    }
                    hashtable[key] = data;
                }
                TNSet2.rules.Add(hashtable);
            }

            // TN - full rule
            TNSet3 = new GpData();
            TNSet3.resultPredict = "TN";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                for (int j = 0; j < 54; ++j)
                {
                    data.setOperatorRule(random.Next(4));
                    data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                    int key = random.Next(54);
                    while (hashtable.ContainsKey(key))
                    {
                        key = random.Next(54);
                    }
                    hashtable[key] = data;
                }
                TNSet3.rules.Add(hashtable);
            }

            // TN - increment rule
            TNSet4 = new GpData();
            TNSet4.resultPredict = "TN";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                for (int j = 0; j < i + 1; ++j)
                {
                    data.setOperatorRule(random.Next(4));
                    data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                    int key = random.Next(54);
                    while (hashtable.ContainsKey(key))
                    {
                        key = random.Next(54);
                    }
                    hashtable[key] = data;
                }
                TNSet4.rules.Add(hashtable);
            }

            // BTH - low rule
            BTHSet1 = new GpData();
            BTHSet1.resultPredict = "BTH";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                data.setOperatorRule(random.Next(4));
                data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                int key = random.Next(54);
                while (hashtable.ContainsKey(key))
                {
                    key = random.Next(54);
                }
                hashtable[key] = data;
                BTHSet1.rules.Add(hashtable);
            }

            // BTH - medium rule
            BTHSet2 = new GpData();
            BTHSet2.resultPredict = "BTH";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                for (int j = 0; j < 28; ++j)
                {
                    data.setOperatorRule(random.Next(4));
                    data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                    int key = random.Next(54);
                    while (hashtable.ContainsKey(key))
                    {
                        key = random.Next(54);
                    }
                    hashtable[key] = data;
                }
                BTHSet2.rules.Add(hashtable);
            }

            // BTH - full rule
            BTHSet3 = new GpData();
            BTHSet3.resultPredict = "BTH";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                for (int j = 0; j < 54; ++j)
                {
                    data.setOperatorRule(random.Next(4));
                    data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                    int key = random.Next(54);
                    while (hashtable.ContainsKey(key))
                    {
                        key = random.Next(54);
                    }
                    hashtable[key] = data;
                }
                BTHSet3.rules.Add(hashtable);
            }

            // BTH - increment rule
            BTHSet4 = new GpData();
            BTHSet4.resultPredict = "BTH";
            for (int i = 0; i < 10; ++i)
            {
                hashtable = new Hashtable();
                for (int j = 0; j < i + 1; ++j)
                {
                    data.setOperatorRule(random.Next(4));
                    data.setScoreValue(roundScore(random.NextDouble() * 10) + "");
                    int key = random.Next(54);
                    while (hashtable.ContainsKey(key))
                    {
                        key = random.Next(54);
                    }
                    hashtable[key] = data;
                }
                BTHSet4.rules.Add(hashtable);
            }
        }

        /**
         * Round 3 number after dot.
         * @param double
         *      number is need to round
         */
        private double roundScore(double d)
        {
            String dicemal = System.Convert.ToString(d);
            int i = 0;
            for (i = 0; i < dicemal.Length; ++i)
            {
                if (dicemal[i] == '.')
                {
                    break;
                }
            }
            dicemal = dicemal.Substring(0, i + 3);
            return Double.Parse(dicemal);
        }

        /**
         * Crossover on Contiguous data.
         * @param
         *      individual1: individual of population
         *      individual2: individual of population
         */
        private void crossoverContiguous(GpData individual1, GpData individual2)
        {
            // Random number
            Random rand = new Random();
            
            // Random case to crossover
            int next = rand.Next(3);

            // number of rule in individual
            int n;

            // min of rule size
            int minRuleSize;

            // min of rule in individual
            int min;

            // List store temp rule
            ArrayList temp1;
            ArrayList temp2;

            // number of rule in rule list
            int numRule;

            // set of rule index to ensure equal a size.
            HashSet<int> set1;
            HashSet<int> set2;

            // random index in rule list
            int rangeRule;

            // temp individual.
            Hashtable rule1;
            Hashtable rule2;

            // random position in rule list.
            int positionRule;

            switch (next)
            {
                // Swap with the smaller size rule list
                case 0:
                    if (individual1.rules.Count < individual2.rules.Count)
                    {
                        n = individual1.rules.Count;
                    }
                    else
                        if (individual1.rules.Count > individual2.rules.Count)
                        {
                            n = individual2.rules.Count;
                        }
                        else
                        {
                            n = individual1.rules.Count / 2;
                        }

                    temp1 = new ArrayList();
                    temp2 = new ArrayList();

                    for (int i = 0; i < n; ++i)
                    {
                        temp1.Add((Hashtable)individual1.rules[i]);
                        individual1.rules.RemoveAt(i);
                    }

                    for (int i = 0; i < n; ++i)
                    {
                        temp2.Add((Hashtable)individual2.rules[i]);
                        individual2.rules.RemoveAt(i);
                    }

                    for (int i = 0; i < n; ++i)
                    {
                        individual1.rules.Add(temp2[i]);
                        individual2.rules.Add(temp1[i]);
                    }
                    ; break;

                // Swap n rule. n in random
                case 1:
                    if (individual1.rules.Count < individual2.rules.Count)
                    {
                        min = individual1.rules.Count;
                    }
                    else
                    {
                        min = individual2.rules.Count;
                    }

                    // random number of rules will be swap.
                    numRule = rand.Next(1, min / 2);

                    // random list index of rules.
                    set1 = new HashSet<int>();

                    // random list index of rules.
                    set2 = new HashSet<int>();

                    // random add to equal numRule
                    while (set1.Count != numRule)
                    {
                        set1.Add(rand.Next(numRule));
                    }

                    // random add to equal numRule
                    while (set2.Count != numRule)
                    {
                        set2.Add(rand.Next(numRule));
                    }

                    // contain list temp when to use swap.
                    temp1 = new ArrayList();
                    temp2 = new ArrayList();

                    foreach (var i in set1)
                    {
                        int val = set1.ElementAt(i);
                        temp1.Add((Hashtable)individual1.rules[val]);
                        individual1.rules.RemoveAt(val);
                    }

                    foreach (var i in set2)
                    {
                        int val = set2.ElementAt(i);
                        temp2.Add((Hashtable)individual2.rules[val]);
                        individual2.rules.RemoveAt(val);
                    }

                    for (int i = 0; i < numRule; ++i)
                    { 
                        individual1.rules.Add((Hashtable)temp2[i]);
                        individual2.rules.Add((Hashtable)temp1[i]);
                    }
                    ; break;

                // Swap n operand in range of each rule.
                case 2:
                    if (individual1.rules.Count < individual2.rules.Count)
                    {
                        min = individual1.rules.Count;
                    }
                    else
                        if (individual1.rules.Count > individual2.rules.Count)
                        {
                            min = individual2.rules.Count;
                        }
                        else
                        {
                            min = individual1.rules.Count;
                        }
                    n = rand.Next(1, min);

                    set1 = new HashSet<int>();
                    set2 = new HashSet<int>();

                    while (set1.Count != n)
                    {
                        set1.Add(rand.Next(min));
                    }

                    while (set2.Count != n)
                    {
                        set2.Add(rand.Next(min));
                    }

                    for (int i = 0; i < n; ++i)
                    {
                        rule1 = (Hashtable)individual1.rules[set1.ElementAt(i)];
                        rule2 = (Hashtable)individual2.rules[set2.ElementAt(i)];

                        if (rule1.Count < rule2.Count)
                        {
                            minRuleSize = rule1.Count;
                        }
                        else
                            if (rule1.Count > rule2.Count)
                            {
                                minRuleSize = rule2.Count;
                            }
                            else
                            {
                                minRuleSize = rule1.Count / 2;
                            }
                        // random number of rule is swap
                        rangeRule = rand.Next(1, minRuleSize);

                        // random start position in rule list
                        positionRule = rand.Next(minRuleSize - rangeRule);

                        // count index in rules list
                        int cnt = 0;

                        // contain temp rule list
                        Hashtable t1 = new Hashtable();
                        Hashtable t2 = new Hashtable();

                        // contain temp delete list
                        temp1 = new ArrayList();
                        temp2 = new ArrayList();

                        foreach (DictionaryEntry j in rule1)
                        {
                            if (cnt >= positionRule && cnt < positionRule + rangeRule)
                            {
                                t1[j.Key] = (DataRule)j.Value;

                                // add key to delete list
                                temp1.Add(j.Key);
                            }
                            ++cnt;
                            if (cnt >= positionRule + rangeRule)
                            {
                                break;
                            }
                        }

                        cnt = 0;

                        foreach (DictionaryEntry j in rule2)
                        {
                            if (cnt >= positionRule && cnt < positionRule + rangeRule)
                            {
                                t2[j.Key] = (DataRule)j.Value;

                                // add key to delete list
                                temp2.Add(j.Key);
                            }
                            ++cnt;
                            if (cnt >= positionRule + rangeRule)
                            {
                                break;
                            }
                        }

                        for (int j = 0; j < rangeRule; ++j)
                        {
                            rule1.Remove(temp1[j]);
                            rule2.Remove(temp2[j]);
                        }

                        foreach (DictionaryEntry j in temp1)
                        {
                            rule1[j.Key] = j.Value;
                        }

                        foreach (DictionaryEntry j in temp2)
                        {
                            rule2[j.Key] = j.Value;
                        }
                    }

                    ; break;
            }
        }

        /**
         * Mutation on training
         */
        private void mutationContiguous(GpData individual)
        {
            Random rand = new Random();
            // random case
            int next = rand.Next(4);

            // temp rule list
            Hashtable tempRuleList;

            // number of rule list
            int numberRuleList;

            // list random rule list index.
            ArrayList ruleListIndex;

            // size of rule
            int ruleSize;

            // data of rule
            DataRule data = new DataRule();

            switch (next)
            {
                // random add n rule.
                case 0:
                    numberRuleList = rand.Next(1, 15);

                    for (int i = 0; i < numberRuleList; ++i)
                    {
                        tempRuleList = new Hashtable();

                        ruleSize = rand.Next(1, 55);

                        for (int j = 0; j < ruleSize; ++j)
                        {
                            data.setOperatorRule(rand.Next(4));
                            data.setScoreValue(roundScore(rand.NextDouble() * 10) + "");
                            int key = rand.Next(55);

                            while (tempRuleList.ContainsKey(key))
                            {
                                key = rand.Next(55);
                            }

                            tempRuleList[key] = data;
                        }

                        individual.rules.Add(tempRuleList);                        
                    }
                    ; break;

                // random add m element in n rule list.
                case 1:
                    numberRuleList = rand.Next(1, individual.rules.Count + 1);

                    int valueIndex;

                    ruleListIndex = new ArrayList();

                    while (ruleListIndex.Count < numberRuleList)
                    {
                        valueIndex = rand.Next(numberRuleList);

                        if (!ruleListIndex.Contains(valueIndex))
                        {
                            ruleListIndex.Add(valueIndex);
                        }
                    }

                    Hashtable ruleTemp;

                    for (int i = 0; i < numberRuleList; ++i)
                    {
                        ruleTemp = (Hashtable) individual.rules[(int)ruleListIndex[i]];
                        
                        int randomRuleInList = rand.Next(55 - ruleTemp.Count);

                        for (int j = 0; j < randomRuleInList; ++j)
                        {
                            data.setOperatorRule(rand.Next(4));
                            data.setScoreValue(roundScore(rand.NextDouble() * 10) + "");
                            int key = rand.Next(55);

                            while (ruleTemp.ContainsKey(key))
                            {
                                key = rand.Next(55);
                            }

                            ruleTemp[key] = data;
                        }
                    }
                    ; break;

                // remove n rule list in individual
                case 2:
                    numberRuleList = rand.Next(individual.rules.Count / 2);

                    ArrayList tempIndex = new ArrayList();

                    for (int i = 0; i < numberRuleList; ++i)
                    {
                        int value = rand.Next(numberRuleList);

                        while (tempIndex.Contains(value))
                        {
                            value = rand.Next(numberRuleList);
                        }
                        tempIndex.Add(value);
                    }

                    int cnt = 0;

                    foreach (DictionaryEntry i in individual.rules)
                    {
                        if (tempIndex.Contains(cnt))
                        {
                            individual.rules.Remove(i.Key);
                        }

                        ++cnt;
                    }
                    ; break;

                // remove m rule in each n rule list.
                case 3:
                    numberRuleList = rand.Next(individual.rules.Count / 2);

                    tempIndex = new ArrayList();

                    for (int i = 0; i < numberRuleList; ++i)
                    {
                        int value = rand.Next(numberRuleList);

                        while (tempIndex.Contains(value))
                        {
                            value = rand.Next(numberRuleList);
                        }
                        tempIndex.Add(value);
                    }

                    cnt = 0;

                    ArrayList indexInRuleList;

                    foreach (Hashtable i in individual.rules)
                    {
                        if (tempIndex.Contains(cnt))
                        {
                            indexInRuleList = new ArrayList();

                            Hashtable ruleList = (Hashtable)i;

                            int numberIndexInRuleList = rand.Next(ruleList.Count / 2);

                            for (int j = 0; j < numberIndexInRuleList; ++j)
                            {
                                int val = rand.Next(ruleList.Count);

                                while (indexInRuleList.Contains(val))
                                {
                                    val = rand.Next(ruleList.Count);
                                }

                                indexInRuleList.Add(val);
                            }

                            int count = 0;

                            foreach (DictionaryEntry j in ruleList)
                            {
                                if (indexInRuleList.Contains(count))
                                {
                                    ruleList.Remove(j.Key);
                                }

                                ++count;
                            }
                        }

                        ++cnt;
                    } 
                    ; break;
            }
        }

        /**
         * Evaluation for fitness of training for contiguous data.
         * @param GpData
         *      Individual of population.
         */ 
        private double fitnessTrainingContiguous(GpData individual)
        {
            // True Positive
            double tp = 0;

            // True Negative
            double tn = 0;

            // Total Positive
            double p;

            // Total Negative
            double n;

            // Maximum node in a rule list
            double maxNode;

            // Numer node of tree
            double numNode;

            // Weigth for accuracy
            double weigth1 = 0.75;

            // Weigth for comprehensibility
            double weigth2 = 0.25;
            Hashtable hash;

            if (individual.resultPredict == "CTN")
            {
                for (int i = MIN_INDEX_CTN; i <= MAX_INDEX_CTN; ++i)
                {
                    for (int j = 0; j < individual.rules.Count; ++j)
                    {
                        hash = (Hashtable)individual.rules[0];
                    }
                }
            }
            else
                if (individual.resultPredict == "TN")
                {
                }
                else
                {
                }

            //return weigth1 * (tp / p) * (tn / n) + weigth2 * ((maxNode - 0.5) * (numNode - 0.5) / (maxNode - 1));
            return 0.0;
        }

        private bool compareOperatorContiguous(double value, DataRule data)
        {
            if (data.getOperatorRule() == 0)
            {
                return value < Double.Parse(data.getScoreValue());
            }
            if (data.getOperatorRule() == 1)
            {
                return value > Double.Parse(data.getScoreValue());
            }
            if (data.getOperatorRule() == 2)
            {
                return value <= Double.Parse(data.getScoreValue());
            }
            else
            {
                return value >= Double.Parse(data.getScoreValue());
            }
        }
    }
}
